package application.view.gui;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.border.TitledBorder;
import javax.swing.table.DefaultTableModel;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.PlainDocument;

import application.controllers.PeriodController;
import application.controllers.PeriodException;
import application.models.collections.CarCollection;
import application.models.collections.Collection;
import application.models.collections.CustomerCollection;
import application.models.datalayer.Attribute;
import application.models.exceptions.InvalidAttributeException;
import application.models.exceptions.MissingDataException;
import application.models.models.AbstractModel;
import application.models.models.CarModel;
import application.models.models.CustomerModel;
import application.models.models.ReservationModel;
import application.models.datalayer.Date;

/**
 * This class takes care of the graphical user interface elements used to represent a window, from where it is possible to create, edit, or delete a reservation
 * 
 * @author Morten Therkildsen & Philip Rasmussen
 */
public class ReservationView{

	private static final  String DEFAULT_CB_STRING = "<Not specified>";	// The default string when nothing is selected
	private static final String TITLE = "Reservation", PERIOD_SECTION = "Reservation period", CUSTOMER_SECTION = "Customer", CRITERIA_SECTION = "Criteria";	
	private static final Dimension PREFFERED_SIZE = new Dimension(800, 500);
	private static final Dimension MINIMUM_SIZE = PREFFERED_SIZE;
	private static final Collection<CarModel> allCars = new CarCollection();
	private static final String[] columnNames = new String[]{
		"Number Plate", "Car Type", "Car Make", "Car Model", "Fuel Type", "Auto. Gear", 
		"ESP", "Doors", "Seats", "Color", "Boot Capacity"};		// All the column names. 
	private static final String[] datalayerColumnNames = new String[]{
		"number_plate", "car_type", "car_make", "model", "fuel_type", "has_automatic_gear", 
		"has_esp", "door_count", "seat_count", "color", "boot_capacity_m3"}; // All the column names in the data layer. Would be better to have in an enum class
	private static final HashMap<Object, CustomerModel> customerMap = new HashMap<Object, CustomerModel>(); // Maps each customer string in the customer combo box to a customer.

	private static GraphicalUserInterface gui;
	private static boolean applicationStartUp = false, customerlistIsGenerated = false;
	private static Collection<CustomerModel> customers = new CustomerCollection();		// All customers
	private static Object[] customerList;	// A list of customers.
	private static Set<Object> customerSet;	// A set with customers (used for sorting)
	private boolean isNew = false, unsavedChanges = false;	// if the reservation is new, and if there are unsaved changes
	private Set<CarModel> freeCars = new HashSet<CarModel>();	// a set containing available cars
	private JFrame window;
	private ReservationModel reservationCopy, reservation;	// reservationCopy is a backup. So that if reservation suddenly do not match any cars, one can click cancel and the original reservation will apply
	private DefaultTableModel model;
	private HashMap<String, Object[]> selectedItemMap = new HashMap<String, Object[]>();	// Maps selected and deselected items in each combo box whenever an item is selected. 
	private ArrayList<JComboBox> criteriaComboBoxList = new ArrayList<JComboBox>();	// A list of all criteria combo boxes
	private PeriodController periodController;	// Used for dates
	private String fromStringBefore, toStringBefore;	// Used to see if a change has been made to one of the text fields.
	private Quitter quitObject = new Quitter();	// An inner class handling quit requests.

	/**
	 * Typically used for the first instantiation of a ReservationView object. Can only be called once.
	 * 
	 * @param The GUI object that contains information about obligatory window elements and their associated events.
	 */
	public static void startup(GraphicalUserInterface guiObject){

		if(applicationStartUp) 
			return;

		gui = guiObject;
		ReservationView.loadCars(); // Load all registered cars.
		addMenuBarFunctionality();	// Add the Reservation Window specific menu items to the menu bar
		applicationStartUp = true;
	}

	/**
	 * Used to open a new window. Creates a new ReservationView instance.
	 */
	public static void open(){

		ReservationView newReservationView = new ReservationView(0, true);
		newReservationView.focus();
	}

	/**
	 * Used to open a new window based on a specific reservation
	 */
	public static void open(int id){

		ReservationView newReservationView = new ReservationView(id, false);
		newReservationView.focus();
	}

	/**
	 * Private constructor that takes care of everything related to the visualization of a ReservationView object.
	 * 
	 * @param id	The id of the reservation to load. If null, a new reservation is created
	 * @param isNew	True if this reservation is new, false if it is loaded
	 */
	private ReservationView(int id, boolean isNew){

		this.isNew = isNew;
		determineReservationState(id);

		window = new JFrame();
		setWindowProperties();

		JPanel topSection = new JPanel();
		JPanel topSectionInner = new JPanel();
		JPanel bottomSection = new JPanel();

		topSectionInner.setLayout(new BoxLayout(topSectionInner, BoxLayout.X_AXIS));
		topSectionInner.add(createReservationPeriodSection());
		topSectionInner.add(createCustomerSection());

		topSection.setLayout(new BorderLayout());
		topSection.add(topSectionInner, BorderLayout.NORTH);

		updateCriteria();	// Put all columns in the selectedItemMap, with a String[2] as value, where [0] is the deselected item and [1] is the selected item 		

		bottomSection.setLayout(new BorderLayout());
		bottomSection.add(createCriteriaSection(), BorderLayout.NORTH);
		bottomSection.add(createTableSection(), BorderLayout.CENTER);

		topSection.add(bottomSection, BorderLayout.CENTER);
		topSection.add(createButtonSection(), BorderLayout.SOUTH);

		window.add(topSection);
		window.pack();
		window.setVisible(true);
	}

	/**
	 * Puts the window in focus
	 */
	private void focus(){

		window.setVisible(true);
		window.toFront();
	}

	/**
	 * Used to load all the registered cars. This should only need to be called once
	 * Note: If support for creating, deleting, and editing cars are implemented, 
	 * it would be appropriate to call this method more than once
	 */
	private static void loadCars(){

		try{
			allCars.load();
		}catch(IOException e){ // Nothing to do about it
		}
	}

	/**
	 * This method updates the criteria for a reservation. To match the criteria of either a previously saved reservation or a new reservation
	 */
	private void updateCriteria(){

		if(isNew){

			for(int i = 1 ; i < datalayerColumnNames.length ; i++){
				selectedItemMap.put(datalayerColumnNames[i], new Object[]{DEFAULT_CB_STRING, DEFAULT_CB_STRING});

			}

			selectedItemMap.put("Customer", new Object[]{DEFAULT_CB_STRING, new Object()});

		}else{

			for(int i = 1 ; i < datalayerColumnNames.length ; i++){

				try {

					if(reservationCopy.getAttribute(datalayerColumnNames[i]) == null){
						selectedItemMap.put(datalayerColumnNames[i], new Object[]{DEFAULT_CB_STRING, DEFAULT_CB_STRING});

					}else{
						selectedItemMap.put(datalayerColumnNames[i], new Object[]{reservationCopy.getAttribute(datalayerColumnNames[i]), reservationCopy.getAttribute(datalayerColumnNames[i])});

					}

				} catch (InvalidAttributeException e) {
				}							
			}
			selectedItemMap.put("Customer", new Object[]{DEFAULT_CB_STRING, customerMap.get(reservationCopy.getCustomer().getId())});
		}
	}

	/**
	 * This method updates all the available cars
	 */
	private void updateAvailableCars(){

		if(!isNew){

			reservation.delete();

			try{
				freeCars = reservationCopy.getMatchingFreeCars();

			} catch(MissingDataException e){
			} finally{
				try {
					reservation.save();
				} catch (MissingDataException e) {
				}
			}			
		}

		if(isNew){
			try {
				freeCars = reservationCopy.getMatchingFreeCars();
			} catch (MissingDataException e) {
			}
		}
	}

	/**
	 * Determine the properties of the window.
	 */
	private void setWindowProperties(){

		window.setTitle(TITLE);
		window.setMinimumSize(MINIMUM_SIZE);
		window.setPreferredSize(PREFFERED_SIZE);
		window.setJMenuBar(new MenuBuilder(gui.getSharedMenuBuilder().getMenuBar()).getMenuBar());
	}

	/**
	 * This method created the visual representation and all the properties of 
	 * the Reservation Period section of the reservation window. 
	 * 
	 * @return A panel containing the Reservation Period section of the reservation window.
	 */
	private JPanel createReservationPeriodSection(){

		TitledBorder periodBorderTitle = BorderFactory.createTitledBorder(PERIOD_SECTION); 	// Create a titled border object

		JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT));
		panel.setBorder(periodBorderTitle);  		
		panel.add(new JLabel("From: "));
		
		JTextField fromField = new JTextField();
		fromField.setDocument(new PlainDocument(){

			private static final long serialVersionUID = -200088186613511523L;


			public void insertString(int offset, String string, AttributeSet attributes) throws BadLocationException {

				if(string == null) return;

				String all = getText(0, getLength());
				String before = all.substring(0, offset);
				String after = all.substring(offset, all.length());

				// Validate
				if(!(before+string+after).matches("^([0-9]|/){0,10}$")) return;

				super.insertString(offset, string, attributes);
			}
		});

		try {fromField.getDocument().insertString(0, periodController.getFromDateAsString(), null);}
		catch(BadLocationException e){throw new RuntimeException("Unexpected logic failure.", e);}
		
		fromField.addFocusListener(new FocusListener(){

			public void focusGained(FocusEvent event){


				JTextField field = (JTextField) event.getComponent();
				Document document = field.getDocument();
				try {
					fromStringBefore = document.getText(0, document.getLength());	// Used to compare the string content when focus is gained and when focus is lost
				} catch (BadLocationException e) {
				}
			}

			public void focusLost(FocusEvent event){

				JTextField field = (JTextField) event.getComponent();
				Document document = field.getDocument();
				try {

					periodController.setFromDate(document.getText(0, document.getLength()));

					if(!fromStringBefore.equals(document.getText(0, document.getLength())))
						unsavedChanges = true;	// Only registers the change if the string content has changed.

					try{
						reservationCopy.setAttribute("starts", periodController.getFromDate());
						updateAvailableCars();

					}catch(InvalidAttributeException ex){
					}

					updateComboBoxes();
					updateTable();
					return;

				} catch(PeriodException e){

					String message =	"The specified date was invalid. Please check that:\n\n" +
					"- The date follows the format DD/MM/YYYY\n" +
					"- \"From\" is a date coming before \"To\"\n\n" +
					"Please try again.";  		

					JOptionPane.showMessageDialog(window, message, "Invalid \"From\" date", JOptionPane.ERROR_MESSAGE);

					try{

						document.remove(0, document.getLength());
						document.insertString(0, periodController.getFromDateAsString(), null);
						field.grabFocus();

					} catch(BadLocationException e2){}

				} catch(BadLocationException e){}
			}
		});
		
		panel.add(fromField);
		panel.add(new JLabel("To: "));
		
		JTextField toField = new JTextField();
		toField.setDocument(new PlainDocument(){

			private static final long serialVersionUID = -2701865074794420044L;

			public void insertString(int offset, String string, AttributeSet attributes) throws BadLocationException {

				if(string == null) return;

				String all = getText(0, getLength());
				String before = all.substring(0, offset);
				String after = all.substring(offset, all.length());

				// Validate
				if(!(before+string+after).matches("^([0-9]|/){0,10}$")) return;

				super.insertString(offset, string, attributes);
			}
		});

		try {toField.getDocument().insertString(0, periodController.getToDateAsString(), null);}
		catch(BadLocationException e){throw new RuntimeException("Unexpected logic failure.", e);}

		toField.addFocusListener(new FocusListener(){

			public void focusGained(FocusEvent event){
				JTextField field = (JTextField) event.getComponent();
				Document document = field.getDocument();
				try {
					toStringBefore = document.getText(0, document.getLength());	// Used to compare the string content when focus is gained and when focus is lost
				} catch (BadLocationException e) {
				}
			}

			public void focusLost(FocusEvent event){

				JTextField field = (JTextField) event.getComponent();
				Document document = field.getDocument();
				try {

					periodController.setToDate(document.getText(0, document.getLength()));

					if(!toStringBefore.equals(document.getText(0, document.getLength())))
						unsavedChanges = true;	// Only registers the change if the string content has changed.

					try{
						reservationCopy.setAttribute("ends", periodController.getToDate());
						updateAvailableCars();
					}catch(InvalidAttributeException ex){
					} 

					updateComboBoxes();
					updateTable();	
					return;
				} catch(PeriodException e){

					String message =	"The specified date was invalid. Please check that:\n\n" +
					"- The date follows the format DD/MM/YYYY\n" +
					"- \"From\" is a date coming before \"To\"\n\n" +
					"Please try again.";   		

					JOptionPane.showMessageDialog(window, message, "Invalid \"To\" date", JOptionPane.ERROR_MESSAGE);

					try{

						document.remove(0, document.getLength());
						document.insertString(0, periodController.getToDateAsString(), null);
						field.grabFocus();

					} catch(BadLocationException e2){}

				} catch(BadLocationException e){}
			}
		});
		
		panel.add(toField);

		return panel;
	}

	/**
	 * This method determines if the reservation is new, or if it's already in the system, and what to to about it
	 * It also initalizes all the necessary values for the reservation.
	 */
	private void determineReservationState(int id){

		if(isNew){ 

			reservation = new ReservationModel();	
			reservationCopy = new ReservationModel();

			try{

				periodController = new PeriodController(7);		// Sets the default period of 7 days.
				reservation.setAttribute("starts", periodController.getFromDate());
				reservation.setAttribute("ends", periodController.getToDate());

			} catch(PeriodException e){throw new RuntimeException("Unexpected logic error", e);
			} catch (InvalidAttributeException e) {
			}

		}else{
			try{

				reservation = ReservationModel.load(id);
				reservationCopy = new ReservationModel();
				periodController = new PeriodController((Date) reservation.getAttribute("starts"), (Date) reservation.getAttribute("ends"));

			} catch (PeriodException e) { // This would be odd
			} catch (InvalidAttributeException e) {	// Will only happen if the Date class changes or the reservation attributes changes
			}
		}

		copyReservationAttributesFrom(reservation, reservationCopy);	// Copy all attribute values from the reservation ro a copy of the reservation
		updateAvailableCars();
	}

	/**
	 * This method created the visualization and all properties of the customer section.
	 * @return The panel containing the visualization and all properties of the customer section.
	 */
	private JPanel createCustomerSection(){

		TitledBorder customerBorderTitle = BorderFactory.createTitledBorder(CUSTOMER_SECTION);
		JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT));
		panel.setBorder(customerBorderTitle);
		panel.add(new JLabel("Customer number: "));

		if(!customerlistIsGenerated)
			generateCustomerListAndSet();

		JComboBox customerCB = new JComboBox(customerList);
		
		if(!isNew){
			
			CustomerModel customer = reservationCopy.getCustomer();
			String customerString = "";
			
			try {

				customerString = customer.getId() + " - " + customer.getAttribute("last_name") + ", " + customer.getAttribute("first_name");

				for(int i = 0 ; i < customerList.length ; i++){
					if(((String)((JComboBox)customerCB).getItemAt(i)).equals(customerString)){

						customerCB.setSelectedIndex(i);
						break;
					}
				}
			}catch(InvalidAttributeException e){
			}
		}
		
		addCustomerCBListener(customerCB);
		panel.add(customerCB);

		return panel;
	}

	private void addCustomerCBListener(JComboBox customerCB){

		customerCB.addItemListener(new ItemListener() {

			public void itemStateChanged(ItemEvent e) {

				Object[] selectedItemArray = selectedItemMap.get("Customer");				
				selectedItemArray[1] = (Object) ((JComboBox) e.getSource()).getSelectedItem();

				if(e.getStateChange() == ItemEvent.SELECTED){

					if(!selectedItemArray[1].equals(DEFAULT_CB_STRING)){

						try{
							reservationCopy.setAttribute("customer_no", customerMap.get(selectedItemArray[1]).getId());

						}catch(InvalidAttributeException ex){
						}	

					}else{

						try{
							reservationCopy.setAttribute("customer_no", null);

						}catch(InvalidAttributeException ex){
						}	

					}					
				}

				unsavedChanges = true;
				selectedItemArray[0] = selectedItemArray[1];
				selectedItemMap.remove("Customer");
				selectedItemMap.put("Customer", selectedItemArray);
				sortItems(customerSet, selectedItemArray[0]);
			}
		});

	}
	
	/**
	 * This method generates the list of customers an initializes it to String[] and to Set<String>
	 */
	private void generateCustomerListAndSet(){
		/* Generate strings for all customers in the format: customer_no - last_name, first_name */
		try{			
			customers.load();

		} catch(IOException e){
		}

		ArrayList<CustomerModel> customerArray = new ArrayList<CustomerModel>();
		Iterator<CustomerModel> customerIterator = customers.iterator();

		while(customerIterator.hasNext())
			customerArray.add(customerIterator.next());
	
		customerSet = new HashSet<Object>();

		for(int i = 0 ; i < customers.size() ; i++){
			
			try{
				
				String customer = customerArray.get(i).getId() + " - " + customerArray.get(i).getAttribute("last_name") + ", " + customerArray.get(i).getAttribute("first_name");
				customerSet.add(customer);
				customerMap.put(customer, customerArray.get(i));
			
			}catch(InvalidAttributeException e){	
			}
		}

		customerList = sortItems(customerSet, DEFAULT_CB_STRING);
		customerlistIsGenerated = true;		
	}

	/**
	 * This method creates the criteria section of the reservation view
	 * @return a JPanel containing the criteria section of the reservation view
	 */
	private JPanel createCriteriaSection(){

		/* Initialize the panel and set the border */
		JPanel criteriaPanel = new JPanel();
		TitledBorder panelBorder = BorderFactory.createTitledBorder(CRITERIA_SECTION);
		criteriaPanel.setBorder(panelBorder);
		criteriaPanel.setLayout(new BoxLayout(criteriaPanel, BoxLayout.X_AXIS));

		/* Which each contains 4 box layout panels */
		JPanel firstPanel = new JPanel(new GridLayout(4, 1, 10, 10));
		JPanel secondPanel = new JPanel(new GridLayout(4, 1, 10, 10));
		JPanel thirdPanel = new JPanel(new GridLayout(4, 1, 10, 10));
		JPanel fourthPanel = new JPanel(new GridLayout(4, 1, 10, 10));
		JPanel fifthPanel = new JPanel(new GridLayout(4, 1, 10, 10));
		JPanel sixthPanel = new JPanel(new GridLayout(4, 1, 10, 10));

		/* The 4 labels for top left */
		JLabel typeLabel = new JLabel("Type: ", JLabel.RIGHT);
		JLabel makeLabel = new JLabel("Make: ", JLabel.RIGHT);
		JLabel modelLabel = new JLabel("Model: ", JLabel.RIGHT);
		JLabel fuelLabel = new JLabel("Fuel: ", JLabel.RIGHT);

		/* The 2 labels for the top middle */
		JLabel gearLabel = new JLabel("Automatic gear: ", JLabel.RIGHT);
		JLabel espLabel = new JLabel("ESP: ", JLabel.RIGHT);

		/* The 4 labels for top right */
		JLabel doorLabel = new JLabel("Doors: ", JLabel.RIGHT);
		JLabel seatLabel = new JLabel("Seats: ", JLabel.RIGHT);
		JLabel colorLabel = new JLabel("Color: ", JLabel.RIGHT);
		JLabel capacityLabel = new JLabel("Boot capacity: ", JLabel.RIGHT);	

		/* The 10 combo boxes */
		/* The Type combo box */
		JComboBox typeCB = new JComboBox();
		criteriaComboBoxList.add(typeCB);

		/* The Make combo box */
		JComboBox makeCB = new JComboBox();
		criteriaComboBoxList.add(makeCB);

		/* The Model combo box */
		JComboBox modelCB = new JComboBox();
		criteriaComboBoxList.add(modelCB);

		/* The Fuel combo box */
		JComboBox fuelCB = new JComboBox();
		criteriaComboBoxList.add(fuelCB);

		/* The Automatic gear combo box */
		JComboBox gearCB = new JComboBox();
		criteriaComboBoxList.add(gearCB);

		/* The ESP combo box */
		JComboBox espCB = new JComboBox();
		criteriaComboBoxList.add(espCB);

		/* The Door combo box */
		JComboBox doorCB = new JComboBox();
		criteriaComboBoxList.add(doorCB);

		/* The Seat combo box */
		JComboBox seatCB = new JComboBox();
		criteriaComboBoxList.add(seatCB);

		/* The Color combo box */
		JComboBox colorCB = new JComboBox();
		criteriaComboBoxList.add(colorCB);

		/* The Capacity combo box */
		JComboBox capacityCB = new JComboBox();
		criteriaComboBoxList.add(capacityCB);

		/* Add listeners to all comboboxes */
		updateComboBoxes();

		/* Inserting in the vertical panels */
		firstPanel.add(typeLabel);
		firstPanel.add(makeLabel);
		firstPanel.add(modelLabel);
		firstPanel.add(fuelLabel);

		secondPanel.add(typeCB);
		secondPanel.add(makeCB);
		secondPanel.add(modelCB);
		secondPanel.add(fuelCB);

		thirdPanel.add(gearLabel);
		thirdPanel.add(espLabel);

		fourthPanel.add(gearCB);
		fourthPanel.add(espCB);

		fifthPanel.add(doorLabel);
		fifthPanel.add(seatLabel);
		fifthPanel.add(colorLabel);
		fifthPanel.add(capacityLabel);

		sixthPanel.add(doorCB);
		sixthPanel.add(seatCB);
		sixthPanel.add(colorCB);
		sixthPanel.add(capacityCB);

		/* Inserting in the criteria panel */
		criteriaPanel.add(firstPanel);
		criteriaPanel.add(secondPanel);
		criteriaPanel.add(thirdPanel);
		criteriaPanel.add(fourthPanel);
		criteriaPanel.add(fifthPanel);
		criteriaPanel.add(sixthPanel);

		return criteriaPanel;
	}

	/**
	 * Creates the entire table section
	 * @return The container containing the entire table section
	 */
	private Container createTableSection(){

		JScrollPane scrollPane = new JScrollPane();
		model = new DefaultTableModel();
		for(int i = 0 ; i < columnNames.length ; i++){

			model.addColumn(columnNames[i]);
		}

		JTable table = generateTable(model);

		// Populate tables with data
		updateTable();

		scrollPane.setViewportView(table);

		return scrollPane;
	}

	/**
	 * This method creates the bottom right button section
	 * @return A JPanel containing the bottom right button section
	 */
	private JPanel createButtonSection(){

		JPanel panel = new JPanel(new FlowLayout(FlowLayout.RIGHT));

		JButton cancelButton = new JButton("Cancel");
		cancelButton.addActionListener(new ActionListener(){

			public void actionPerformed(ActionEvent event){

				cancel();

			}
		});
		panel.add(cancelButton);

		JButton deleteButton = new JButton("Delete");
		deleteButton.addActionListener(new ActionListener(){

			public void actionPerformed(ActionEvent event){

				confirm();
			}
		});
		/* If this is a new reservation, the delete button is disabled.  */
		if(isNew)
			deleteButton.setEnabled(false);

		panel.add(deleteButton);

		String saveOrCreate = "";

		if(isNew)
			saveOrCreate = "Create";
		else
			saveOrCreate = "Save";

		JButton saveOrCreateButton = new JButton(saveOrCreate);
		saveOrCreateButton.addActionListener(new ActionListener(){

			public void actionPerformed(ActionEvent event){

				save();
			}
		});
		panel.add(saveOrCreateButton);

		return panel;
	}

	/**
	 * This method duplicates the attribute values of the fromModel to the toModel
	 * 
	 * @param fromModel The reservation for which the attribute values are to be duplicated
	 * @param toModel	The reservation to have its attribute values modified 
	 */
	private void copyReservationAttributesFrom(ReservationModel fromModel, ReservationModel toModel){

		for(Attribute attribute : fromModel){

			try {
				toModel.setAttribute(attribute.getName(), attribute.getValue());
			} catch (InvalidAttributeException e) {
			}
		}
	}

	/**
	 * This method is used to cancel
	 */
	private void cancel(){
		window.setVisible(false);
		reservationCopy.delete();
	}

	/**
	 * This method is used to confirm an operation
	 */
	private void confirm(){

		int choice = JOptionPane.showConfirmDialog(window, "Are you sure you want to delete customer reservation: " + reservation.getId() + "?", "Confirm", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);

		if(choice == JOptionPane.YES_OPTION){
			reservationCopy.delete();
			reservation.delete();
			window.setVisible(false);
		}
		quitObject.requestQuit();		
	}

	/**
	 * This method initiates the save procedure
	 */
	private void save(){

		if(freeCars.size() == 0){
			JOptionPane.showMessageDialog(window, "Failed to save reservation.", "Operation failed" , JOptionPane.OK_OPTION);
			return;
		}

		try{

			copyReservationAttributesFrom(reservationCopy, reservation);
			reservationCopy.delete();
			reservation.save();

		}catch(MissingDataException e){	JOptionPane.showMessageDialog(window, "Failed to save reservation.", "Operation failed" , JOptionPane.OK_OPTION); return;
		}

		unsavedChanges = false;
		window.setVisible(false);
	}

	/**
	 * Adds an item listener to a combobox object
	 * @param comboBox the combobox to attach the listener to
	 * @param columnName the name of the attribute to search for
	 */
	private void addCBListener(JComboBox comboBox, final String columnName){
		comboBox.addItemListener(new ItemListener() {

			public void itemStateChanged(ItemEvent e) {

				Object[] selectedItemArray = selectedItemMap.get(columnName);				
				selectedItemArray[1] = (Object) ((JComboBox) e.getSource()).getSelectedItem();

				if(e.getStateChange() == ItemEvent.SELECTED){

					/* Don't remove a filter if the deselected item is DEFAULT_CB_STRING (there's no filter to remove in this case) 
					 * Not necessary, but included for clarity */
					if(!selectedItemArray[0].equals(DEFAULT_CB_STRING)){

						try{
							reservationCopy.setAttribute(columnName, null);

						}catch(InvalidAttributeException ex){
						}
					}

					/* Don't add a filter if the selected item is DEFAULT_CB_STRING */
					if(!selectedItemArray[1].equals(DEFAULT_CB_STRING)){

						try{
							reservationCopy.setAttribute(columnName, selectedItemArray[1]);

						}catch(InvalidAttributeException ex){ // The selected item is not an attribute in the data layer.						}
						}						
					}

					updateAvailableCars();
					selectedItemArray[0] = selectedItemArray[1];
					selectedItemMap.remove(columnName);
					selectedItemMap.put(columnName, selectedItemArray);
					updateComboBoxes();
					updateTable();	
					unsavedChanges = true;
				}
			}
		});
	}

	/**
	 * This method updates all combo boxes in the reservation view
	 */
	private void updateComboBoxes(){

		for(int i = 0 ; i < criteriaComboBoxList.size(); i++){			
			/* Remove any item listeners from the combo box */
			ItemListener[] listeners = criteriaComboBoxList.get(i).getItemListeners();

			for(ItemListener listener : listeners)
				criteriaComboBoxList.get(i).removeItemListener(listener);

			/* Get the updated list */
			Object[] updatedList = freeCarsListHelper(freeCars, datalayerColumnNames[i+1], selectedItemMap.get(datalayerColumnNames[i+1])[0]);
			/* Remove all items in the combo box */
			criteriaComboBoxList.get(i).removeAllItems();

			/* Insert all items in the updated list into the combo box */
			for(int j = 0 ; j < updatedList.length ; j++){
				criteriaComboBoxList.get(i).addItem(updatedList[j]);

				/* If the j'th object equals the selected object. The j'th object is set to be selected in the JComboBox */
				if(updatedList[j].equals(selectedItemMap.get(datalayerColumnNames[i+1])[1]))
					criteriaComboBoxList.get(i).setSelectedIndex(j);
			}

			/* Add a new item listener to the combo box */
			addCBListener(criteriaComboBoxList.get(i), datalayerColumnNames[i+1]);
		}

	}

	/**
	 * This method generates the table in the reservation view
	 * @param A table object
	 * @return A JTable object
	 */
	private JTable generateTable(DefaultTableModel model){

		JTable table = new JTable(model) {

			private static final long serialVersionUID = -6846471394909693566L;

			// Disable cell edits
			public boolean isCellEditable(int rowIndex, int vColIndex) {

				return false;
			}
		};

		table.setColumnSelectionAllowed(false);
		table.setDragEnabled(false);
		table.setShowGrid(true);
		table.getTableHeader().setReorderingAllowed(false);
		table.getTableHeader().setResizingAllowed(false);
		table.setFocusable(false);
		table.setCellSelectionEnabled(false);
		table.setGridColor(Color.BLACK);

		return table;
	}

	/**
	 * This method takes care of updating the table in the reservation view.
	 */
	private void updateTable(){
		int rows = model.getRowCount();
		for(int i = 0 ; i < rows ; i++){
			model.removeRow(0);
		}

		Iterator<CarModel> iterator = freeCars.iterator();

		while(iterator.hasNext()){	
			iterator.next();

		}

		for(AbstractModel item : freeCars){
			AbstractModel elementModel = (AbstractModel) item;

			try{
				model.addRow(new Object[]{
						elementModel.getAttribute(datalayerColumnNames[0]).toString(),
						elementModel.getAttribute(datalayerColumnNames[1]).toString(), 
						elementModel.getAttribute(datalayerColumnNames[2]).toString(), 
						elementModel.getAttribute(datalayerColumnNames[3]).toString(), 
						elementModel.getAttribute(datalayerColumnNames[4]).toString(), 
						elementModel.getAttribute(datalayerColumnNames[5]).toString(), 
						elementModel.getAttribute(datalayerColumnNames[6]).toString(), 
						elementModel.getAttribute(datalayerColumnNames[7]).toString(),
						elementModel.getAttribute(datalayerColumnNames[8]).toString(),
						elementModel.getAttribute(datalayerColumnNames[9]).toString(),
						elementModel.getAttribute(datalayerColumnNames[10]).toString()});

			}catch(InvalidAttributeException e){
			}
		}
	}

	/**
	 * This is a specified method that creates and initializes a 'New Reservation' menu item
	 * @return
	 */
	private static JMenuItem generateNewReservationMenuItem(){

		JMenuItem newMenuItem = new JMenuItem("New Reservation");
		addNewReservationListener(newMenuItem);
		return newMenuItem;
	}

	/**
	 * This is a specialized method that adds an action listener to a 'New Reservation' menu item
	 * @param The menu item
	 */
	private static void addNewReservationListener(JMenuItem item){

		item.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent e){

				ReservationView.open();
			}
		});
	}

	/**
	 * This is a specialized method that adds ReservationView functionality to the default menubar
	 */
	private static void addMenuBarFunctionality(){

		try{
			gui.getSharedMenuBuilder().addMenuItem("File", generateNewReservationMenuItem());

		} catch(DuplicateMenuItemException e){ /* Ignore in this case */			
		}
	}

	/**
	 * This method finds all the available cars for a reservation and returns them as an object array
	 * 
	 * @param set The set containing the cars
	 * @param attributeName	The name of the attribute to search for
	 * @param previouslySelected	The previously selected combo box item
	 * @return An array of objects
	 */
	private Object[] freeCarsListHelper(Set<CarModel> set, String attributeName, Object previouslySelected){

		Set<Object> values = new HashSet<Object>();

		for(CarModel model : set){

			try{ 

				values.add(model.getAttribute(attributeName));

			} catch(InvalidAttributeException e){ /* Ignore */ }
		}

		return sortItems(values, previouslySelected);
	}

	/**
	 * This method is used to sort a set of objects
	 * 
	 * @param values	The set of objects to sort
	 * @param previouslySelected	The previously selected combo box item
	 * @return	A sorted array
	 */
	private Object[] sortItems(Set<Object> values, Object previouslySelected){

		Object[] sortedValues = sort(values.toArray(new Object[0]));
		Object[] result = new Object[sortedValues.length+1];

		int currentIndex = 0;

		result[currentIndex++] = DEFAULT_CB_STRING;

		for(Object value : sortedValues){

			result[currentIndex++] = value;
		}

		return result;
	}

	/**
	 * This method sorts an array of objects
	 * 
	 * @param The array of objects to sort
	 * @return	An array of sorted objects
	 */
	private Object[] sort(Object[] objects){

		if(objects == null || objects.length == 0) return objects;

		sortHelper(objects, 0, objects.length-1);

		return objects;
	}

	/**
	 * This method uses QuickSort on objects lexicographically based on their toString() method
	 * 
	 * @param objects The array of objects to sort
	 * @param	the start index
	 * @param	the end index
	 * @return	An array of sorted objects
	 */
	private void sortHelper(Object[] objects, int fromKeyIndex, int toKeyIndex){

		if (fromKeyIndex >= toKeyIndex) return;

		int lowIndex = fromKeyIndex;
		int highIndex = toKeyIndex;

		Object pivotValueObject = objects[(lowIndex + highIndex) / 2];
		String pivotValue = (pivotValueObject == null ? null : pivotValueObject.toString());

		// Sorts all values higher than the pivotValue after the index of the pivot
		// Sorts all values lower than the pivotValue before the index of the pivot
		while(lowIndex < highIndex){

			// Find the index of the first value greater than the pivotValue from the start
			while(
					lowIndex < highIndex &&
					0 > (objects[lowIndex] == null ? 0 : objects[lowIndex].toString().compareTo(pivotValue)) 
			) ++lowIndex;

			// Find the index of the first value less than the pivotValue from the end
			while(
					lowIndex < highIndex &&
					0 < (objects[highIndex] == null ? 0 : objects[highIndex].toString().compareTo(pivotValue)) 
			) --highIndex;

			// Swap the two values being at the wrong side of the pivot
			if(lowIndex < highIndex){

				Object temp = objects[highIndex];
				objects[highIndex] = objects[lowIndex];
				objects[lowIndex] = temp;
			}

		}

		// Sort the upper and lower parts separately
		sortHelper(objects, fromKeyIndex, lowIndex);
		sortHelper(objects, lowIndex == fromKeyIndex ? lowIndex+1 : lowIndex, toKeyIndex);
	}

	/**
	 * This inner class handles quit requests. It cannot exists without a reservation object.
	 * It is done to make the code more elegant and clear.
	 * 
	 * @author Morten F. Therkildsen & Philip J. Rasmussen
	 */
	private class Quitter{

		/**
		 * Is called by the application when it is quitting. The quit process may be be stopped based on the return value.
		 * 
		 * @return true if there are no unsaved changes, or if the user disregards the unsaved changes, false if the use has want to save any changes first
		 */
		public boolean requestQuit() {

			if(unsavedChanges){
				int choice = JOptionPane.showConfirmDialog(window, "Are you sure you want to quit?\nUnsaved data will be lost!", "Confirm", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);

				if(choice == JOptionPane.NO_OPTION)
					return false;
				
				if(choice == JOptionPane.YES_OPTION){
					unsavedChanges = false;
					window.setVisible(false);
					return true;
				}
			}
			
			return true;
		}
	}
}